{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![bitstring](https://raw.githubusercontent.com/scott-griffiths/bitstring/main/doc/bitstring_logo_small.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# A Walkthrough of the bitstring Module"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "The aim of the `bitstring` module is make dealing with binary data in Python as easy as possible.\n",
    "In this notebook we'll go through some features of the module to help you get started using it.\n",
    "\n",
    "Only a few of the module's features will be covered in this walkthrough; see the [documentation](https://bitstring.readthedocs.io/en/stable/index.html) for a more thorough guide.\n",
    "The whole of this notebook can be safely skipped if you prefer to start with the manual."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Getting started\n",
    "\n",
    "First we need to install the module, which is just a call to `pip install bitstring`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": true
    }
   },
   "outputs": [],
   "source": [
    "%pip install bitstring\n",
    "import bitstring\n",
    "from bitstring import BitArray\n",
    "import math"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "We've imported the `BitArray` class directly to save some typing.\n",
    "This is just a mutable container for binary data. Later on we'll also use the `BitStream` class which adds a bit position and reading methods to treat the data as a stream.\n",
    "There are also immutable versions of both these classes that we won't be using here.\n",
    "\n",
    "We can now create a couple of bitstrings:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = BitArray('0xff01')\n",
    "b = BitArray('0b110')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first of these we made from the hexadecimal string `0xff01` - the `0x` prefix makes it hexadecimal just as `0b` means binary and `0o` means octal.\n",
    "Each hex digit represents four bits, so we have a bitstring of length 16 bits.\n",
    "\n",
    "The second was created from a binary string.\n",
    "In this case it is just three bits long.\n",
    "Don't worry about it not being a whole number of bytes long, that's all been taken care of internally.\n",
    "\n",
    "<div class=\"alert alert-block alert-info\">\n",
    "<b>Note:</b>\n",
    "\n",
    "Be sure to remember the quotes around the hex and binary strings.\n",
    "If you forget them you would just have an ordinary Python integer, which would instead create a bitstring of that many '0' bits.\n",
    "For example `0xff01` is the same as the base-10 number 65281, so `BitArray(0xff01)` would consist of 65281 zero bits!\n",
    "</div>\n",
    "\n",
    "There are lots of things we can do with our new bitstrings, the simplest of which is just to print them:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0xff01\n",
      "0b110\n"
     ]
    }
   ],
   "source": [
    "print(a)\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now you would be forgiven for thinking that the strings that we used to create the two bitstrings had just been stored to be given back when printed, but that's not the case.\n",
    "Every bitstring should be considered just as a sequence of bits.\n",
    "As we'll see there are lots of ways to create and manipulate them, but they have no memory of how they were created.\n",
    "When they are printed they just pick the simplest hex or binary representation of themselves.\n",
    "If you prefer you can pick the representation that you want:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1111111100000001\n",
      "6\n",
      "-2\n",
      "b'\\xff\\x01'\n"
     ]
    }
   ],
   "source": [
    "print(a.bin)\n",
    "print(b.oct)\n",
    "print(b.int)\n",
    "print(a.bytes)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "There are a few things to note here:\n",
    "\n",
    "* To get the different interpretations of the binary data we use properties such as `bin`, `hex`, `oct`, `int` and `bytes`. You can probably guess what these all mean, but you don't need to know quite yet. The properties are calculated when you ask for them rather than being stored as part of the object itself.\n",
    "* Many of the interpretations have single letter aliases, and interpretations can also have bit lengths appended to them. This allows expressions such as `a.u32 = 900` which will set `a` to the 32 bit representation of the unsigned integer `900`. You're not restricted to the usual bit lengths, so something like `a.i5 = -8` will work as well.\n",
    "\n",
    "Great - let's try some more:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cannot convert to hex unambiguously - not a multiple of 4 bits long.\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    b.hex\n",
    "except bitstring.InterpretError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Oh dear - this throws a `bitstring.InterpretError` error.\n",
    "The problem we have here is that `b` is 3 bits long, whereas each hex digit represents 4 bits.\n",
    "This means that there is no unambiguous way to represent it in hexadecimal.\n",
    "There are similar restrictions on other interpretations (octal must be a multiple of 3 bits, bytes a multiple of 8 bits etc.)\n",
    "\n",
    "An exception is raised rather than trying to guess the best hex representation as there are a multitude of ways to convert to hex.\n",
    "I occasionally get asked why it doesn't just do the 'obvious' conversion, which is invariably what that person expects from his own field of work.\n",
    "This could be truncating bits at the start or end, or padding at the start or end with either zeros or ones.\n",
    "Rather than try to guess what is meant we just raise an exception - if you want a particular behaviour then write it explicitly:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "c\n",
      "6\n"
     ]
    }
   ],
   "source": [
    "print((b + [0]).hex)\n",
    "print(([0] + b).hex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Here we've added a zero bit first to the end and then to the start.\n",
    "Don't worry too much about how it all works, but just to give you a taster the zero bit `[0]` could also have been written as `BitArray([0])`, `BitArray('0b0')`, `BitArray(bin='0')`, `'0b0'` or just `1` (this final option isn't a typo, it means construct a bitstring of length one, with all the bits initialised to zero - it does look a bit confusing though which is why I prefer `[0]` and `[1]` to represent single bits).\n",
    "Take a look at [the auto initialiser](https://bitstring.readthedocs.io/en/stable/creation.html#the-auto-initialiser) for more details."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Modifying bitstrings\n",
    "\n",
    "A `BitArray` can be treated just like a list of bits. You can slice it, delete sections, insert new bits and more using standard index notation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0b111110\n",
      "0b1111111100\n"
     ]
    }
   ],
   "source": [
    "print(a[3:9])\n",
    "del a[-6:]\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "The slicing works just as it does for other containers, so the deletion above removes the final six bits.\n",
    "\n",
    "If you ask for a single item, rather than a slice, a boolean is returned. Naturally enough `1` bits are `True` whereas `0` bits are `False`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "print(a[0])\n",
    "print(a[-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "To join together bitstrings you can use a variety of methods, including `append`, `prepend`, `insert`, and plain `+` or `+=` operations::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "a.prepend('0b01')\n",
    "a.append('0o7')\n",
    "a += '0x06'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Here we first put two bits at the start of `a`, then three bits on the end (a single octal digit) and finally another byte (two hex digits) on the end.\n",
    "\n",
    "Note how we are just using ordinary strings to specify the new bitstrings we are adding.\n",
    "These get converted automatically to the right sequence of bits.\n",
    "\n",
    "<div class=\"alert alert-block alert-info\">\n",
    "<b>Note:</b>\n",
    " The length in bits of bitstrings specified with strings depends on the number of characters, including leading zeros.\n",
    " So each hex character is four bits, each octal character three bits and each binary character one bit.\n",
    " </div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Finding and replacing\n",
    "\n",
    "A `find` is provided to search for bit patterns within a bitstring.\n",
    "You can choose whether to search only on byte boundaries or at any bit position:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "(3,)"
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = BitArray('0xa9f')\n",
    "a.find('0x4f')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we have found the `0x4f` byte in our bitstring, though it wasn't obvious from the hexadecimal as it was at bit position 3.\n",
    "To see this clearer consider this equality:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "True"
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a == '0b101, 0x4f, 0b1'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "in which we've broken the bitstring into three parts to show the found byte.\n",
    "This also illustrates using commas to join bitstring sections."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Constructing a bitstring\n",
    "\n",
    "Let's say you have a specification for a binary file type (or maybe a packet specification etc.) and you want to create a bitstring quickly and easily in Python.\n",
    "For this example I'm going to use a header from the MPEG-2 video standard.\n",
    "Here's how the header is described in the standard:\n",
    "\n",
    "|sequence_header()                  | No. of bits  |  Mnemonic|\n",
    "|-----------------------------------|--------------|----------|\n",
    "|sequence_header_code               | 32           |  bslbf   |\n",
    "|horizontal_size_value              | 12           |  uimsbf  |\n",
    "|vertical_size_value                | 12           |  uimsbf  |\n",
    "|aspect_ratio_information           | 4            |  uimsbf  |\n",
    "|frame_rate_code                    | 4            |  uimsbf  |\n",
    "|bit_rate_value                     | 18           |  uimsbf  |\n",
    "|marker_bit                         | 1            |  bslbf   |\n",
    "|vbv_buffer_size_value              | 10           |  uimsbf  |\n",
    "|constrained_parameters_flag        | 1            |  bslbf   |\n",
    "|load_intra_quantiser_matrix        | 1            |  uimsbf  |\n",
    "|if (load_intra_quantiser_matrix)                             |\n",
    "|{ intra_quantiser_matrix[64] }     | 8*64         |  uimsbf  |\n",
    "|load_non_intra_quantiser_matrix    | 1            |  uimsbf  |\n",
    "|if (load_non_intra_quantiser_matrix)                         |\n",
    "|{ non_intra_quantiser_matrix[64] } | 8*64         |  uimsbf  |\n",
    "|next_start_code()                  |              |          |\n",
    "\n",
    "The mnemonics mean things like uimsbf = 'Unsigned integer, most significant bit first'.\n",
    "\n",
    "So to create a sequence_header for your particular stream with width of 352 and height of 288 you could start like this:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "000001b3160120\n"
     ]
    }
   ],
   "source": [
    "s = BitArray()\n",
    "s.append('0x000001b3')  # the sequence_header_code\n",
    "s.append('uint12=352') # 12 bit unsigned integer\n",
    "s.append('uint12=288')\n",
    "print(s.hex)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "which is fine, but if you wanted to be a bit more concise you could just write"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = BitArray('0x000001b3, uint12=352, uint12=288')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is better, but it might not be a good idea to have the width and height hard-wired in like that.\n",
    "We can make it more flexible by using a format string and the `pack` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "width, height = 352, 288\n",
    "s = bitstring.pack('0x000001b3, 2*u12', width, height)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "where we have also used `2*u12` as shorthand for `uint12, uint12`.\n",
    "\n",
    "The [`pack`](https://bitstring.readthedocs.io/en/stable/functions.html#bitstring.pack) function can also take a dictionary as a parameter which can replace the tokens in the format string.\n",
    "For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "\"BitStream('0x000001b31601201')\""
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fmt = 'sequence_header_code, \\\n",
    "       uint12=horizontal_size_value, \\\n",
    "           uint12=vertical_size_value, \\\n",
    "           uint4=aspect_ratio_information, '\n",
    "           # ...\n",
    "d = {'sequence_header_code': '0x000001b3',\n",
    "         'horizontal_size_value': 352,\n",
    "         'vertical_size_value': 288,\n",
    "         'aspect_ratio_information': 1,\n",
    "         # ...\n",
    "        }\n",
    "\n",
    "s = bitstring.pack(fmt, **d)\n",
    "repr(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Parsing bitstreams\n",
    "\n",
    "You might have noticed that `pack` returned a `BitStream` rather than a `BitArray`.\n",
    "This isn't a problem as the `BitStream` class just adds a few stream-like qualities to `BitArray` which we'll take a quick look at here.\n",
    "\n",
    "The stream-ness of this object is via its bit position, and various reading and peeking methods.\n",
    "First let's try a read or two, and see how this affects the bit position:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "BitStream('0x000001')\n",
      "24\n",
      "b3\n",
      "32\n"
     ]
    }
   ],
   "source": [
    "print(s.pos)\n",
    "print(repr(s.read(24)))\n",
    "print(s.pos)\n",
    "print(s.read('hex8'))\n",
    "print(s.pos)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First we read 24 bits, which returned a new `BitStream` object, then we used a format string to read 8 bits interpreted as a hexadecimal string.\n",
    "We know that the next two sets of 12 bits were created from integers, so to read them back we can say"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "[352, 288]"
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s.readlist('2*u12')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "If you don't want to use a bitstream then you can always use [`unpack`](https://bitstring.readthedocs.io/en/stable/bits.html#bitstring.Bits.unpack).\n",
    "This takes much the same form as [`readlist`](https://bitstring.readthedocs.io/en/stable/constbitstream.html#bitstring.ConstBitStream.readlist) except it just unpacks from the start of the bitstring.\n",
    "For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "[b'\\x00\\x00\\x01\\xb3', 352, 288, 1]"
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s.unpack('bytes4, 2*u12, uint4')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Worked Examples\n",
    "\n",
    "Below are a few examples of using the bitstring module, as I always find that a good example can help more than a lengthy reference manual."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hamming distance\n",
    "\n",
    "The Hamming distance between two bitstrings is the number of bit positions in which the two bitstrings differ.\n",
    "So for example the distance between `0b00110` and `0b01100` is 2 as the second and fourth bits are different.\n",
    "\n",
    "Let's write a function that calculates the Hamming weight of two bitstrings.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "2"
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def hamming_weight(a, b):\n",
    "    return (BitArray(a)^b).count(True)\n",
    "\n",
    "hamming_weight('0b00110', '0b01100')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Er, that's it. The `^` is a bit-wise exclusive or, which means that the bits in `a^b` are only set if they differ in `a` and `b`.\n",
    "The `count` method just counts the number of 1 (or True) bits."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sieve of Eratosthenes\n",
    "\n",
    "The sieve of Eratosthenes is an ancient (and _very_ inefficient) method of finding prime numbers.\n",
    "The algorithm starts with the number 2 (which is prime) and marks all of its multiples as not prime, it then continues with the next unmarked integer (which will also be prime) and marks all of its multiples as not prime.\n",
    "\n",
    "[![Sieve animation](https://upload.wikimedia.org/wikipedia/commons/b/b9/Sieve_of_Eratosthenes_animation.gif)](https://commons.wikimedia.org/wiki/File:Sieve_of_Eratosthenes_animation.svg)\n",
    "\n",
    "So to find all primes under a hundred million you could write:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "There are 5761455 primes less than 100000000,\n",
      "the largest one of which is 99999989\n",
      "and there are 440312 twin primes.\n"
     ]
    }
   ],
   "source": [
    "# Create a BitArray with a hundred million 'one' bits\n",
    "limit = 100_000_000\n",
    "is_prime = BitArray(limit)\n",
    "is_prime.set(True)\n",
    "# Manually set 0 and 1 to be not prime.\n",
    "is_prime.set(False, [0, 1])\n",
    "# For every other integer, if it's set as prime then unset all of its multiples\n",
    "for i in range(2, math.ceil(math.sqrt(limit))):\n",
    "    if is_prime[i]:\n",
    "        is_prime.set(False, range(i*i, limit, i))\n",
    "\n",
    "print(f\"There are {is_prime.count(True)} primes less than {limit},\")\n",
    "print(f\"the largest one of which is {is_prime.rfind('0b1')[0]}\")\n",
    "print(f\"and there are {len(list(is_prime.findall('0b101')))} twin primes.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We find the largest prime with a reverse find ([`rfind`](https://bitstring.readthedocs.io/en/stable/bits.html#bitstring.Bits.rfind)) looking for a single set bit.\n",
    "For twin primes (primes which differ by 2) we use [`findall`](https://bitstring.readthedocs.io/en/stable/bits.html#bitstring.Bits.findall) to look for the binary sequence `101` which returns a generator for the bit positions.\n",
    "\n",
    "To see the pattern of the primes we could use the [pretty print](https://bitstring.readthedocs.io/en/stable/bits.html#bitstring.Bits.pp) method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   0: 00110101 00010100 01010001 00000101 00000100 01010001   35 14 51 05 04 51\n",
      "  48: 00000100 00010100 00010001 01000001 00010000 01000000   04 14 11 41 10 40\n",
      "  96: 01000101 00010100 01000000 00000001 00010000 01010000   45 14 40 01 10 50\n",
      " 144: 00000101 00000100 00010001 00000100 00010100 00000001   05 04 11 04 14 01\n",
      " 192: 01000101 00000000 00010000 00000001 00010100 01000001   45 00 10 01 14 41\n",
      " 240: 01000000 00010000 01000001 00000101 00000100 01010000   40 10 41 05 04 50\n",
      " 288: 00000100 00000000 00010001 01000100 00000000 00010000   04 00 11 44 00 10\n",
      " 336: 01000000 00010100 01000001 00000001 00000100 00010001   40 14 41 01 04 11\n",
      " 384: 00000100 00000100 01000000 01000000 00010100 00000001   04 04 40 40 14 01\n",
      " 432: 01000001 00010000 01000000 01000101 00010000 00000001   41 10 40 45 10 01\n",
      " 480: 00000001 00010000 00010001 00000100 00000000 01010000   01 10 11 04 00 50\n",
      " 528: 00000000 00000100 00010000 00000100 00010000 01010000   00 04 10 04 10 50\n",
      " 576: 01000000 00010000 01000001 01000001 00000100 01010000   40 10 41 41 04 50\n",
      " 624: 00000001 00000000 01010001 00000100 00010100 00000000   01 00 51 04 14 00\n",
      " 672: 01000100 00010000 00010000 00000100 00000100 00000001   44 10 10 04 04 01\n",
      " 720: 00000001 00000100 00010001 00000001 00000100 01000000   01 04 11 01 04 40\n",
      " 768: 01000100 00000000 00010000 00000100 00000000 01010000   44 00 10 04 00 50\n",
      " 816: 00000101 00010100 00000001 00000000 00000100 01010001   05 14 01 00 04 51\n",
      " 864: 00000000 00000100 01010001 00000000 00000000 00010001   00 04 51 00 00 11\n",
      " 912: 00000001 00000000 01000000 01000100 00010000 01000000   01 00 40 44 10 40\n",
      " 960: 00000001 00010000 01000001 00000001 00000100            01 10 41 01 04   \n"
     ]
    }
   ],
   "source": [
    "is_prime[0:1000].pp('bin, hex', width=88)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I'll leave optimising the algorithm as an exercise for the reader, but it illustrates both bit checking and setting.\n",
    "One reason you might want to use a bitstring for this purpose (instead of a plain list for example) is that the million bits only take up a million bits in memory, whereas for a list of integers it would be much more."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Next Steps\n",
    "\n",
    "There is fairly extensive [documentation](https://bitstring.readthedocs.io/en/stable/) available.\n",
    "The [Quick Reference](https://bitstring.readthedocs.io/en/stable/quick_reference.html) is a good place to quickly see what's available.\n",
    "\n",
    "For any discussions / bug reports / feature requests see the homepage on [GitHub](https://github.com/scott-griffiths/bitstring/)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
